<template>
  <n-form
    v-bind="getBindValue"
    require-mark-placement="left"
    :model="formModel"
    ref="formElRef"
    :class="['form-layout', searchFormGridOpt.collapsed && isSearchForm && 'fold-form']"
    :label-placement="labelPlacement"
    :label-width="labelWidth"
  >
    <div :class="['form-layout-wrap', isSearchForm && 'search-form']" ref="search-form">
      <n-grid v-bind="isSearchForm ? searchFormGridOpt : getGrid">
        <template v-for="schema in getSchema" :key="schema.field">
          <n-gi
            :class="['n-gi', schema.hidden && 'hidden']"
            v-bind="isSearchForm ? {} : schema.giProps"
          >
            <n-form-item
              v-if="!schema.hidden"
              :label="
                schema.title || getLabelForm === 'table'
                  ? getTableLabelFn(schema, routeName)
                  : getFormLabelFn(schema, routeName)
              "
              :path="schema.field"
              :class="schema.class || []"
            >
              <!--标签名右侧温馨提示-->
              <template #label v-if="schema.labelMessage || schema.slotLabelName">
                <template v-if="schema.labelMessage">
                  {{
                    schema.title || getLabelForm === 'table'
                      ? getTableLabelFn(schema, routeName)
                      : getFormLabelFn(schema, routeName)
                  }}
                  <n-tooltip trigger="hover" :style="schema.labelMessageStyle">
                    <template #trigger>
                      <n-icon size="18" class="cursor-pointer text-gray-400">
                        <QuestionCircleOutlined />
                      </n-icon>
                    </template>
                    {{
                      getLabelForm === 'table'
                        ? getTableLabelFn(schema, routeName, 'labelMessage')
                        : getFormLabelFn(schema, routeName, 'labelMessage')
                    }}
                  </n-tooltip>
                </template>
                <!--自定义标签渲染-->
                <template v-if="schema.slotLabelName">
                  <slot :name="schema.slotLabelName"></slot>
                </template>
              </template>

              <DFFormItem
                :schema="schema"
                :formModel="formModel"
                :is-search-form="isSearchForm"
                :getProps="getProps"
                :field="schema.field"
                @handleSubmitFn="handleSubmit"
                @setFieldsValueFn="setFieldsValue"
              >
                <template v-for="(_, slot) in $slots" #[slot]="{ model }">
                  <slot :name="slot" :model="model"></slot>
                </template>
              </DFFormItem>
            </n-form-item>
          </n-gi>
        </template>
      </n-grid>
    </div>
    <!--提交 重置 展开 收起 按钮-->
    <div class="form-layout-btn defaultButtonClass" v-if="getProps.showActionButtonGroup">
      <n-space
        align="center"
        :justify="isInline ? 'end' : 'start'"
        :style="{ 'margin-left': `${isInline ? 12 : getProps.labelWidth}px`, 'margin-top': '-2px' }"
      >
        <n-button
          v-if="getProps.showSubmitButton"
          v-bind="(getSubmitBtnOptions as any)"
          @click="handleSubmit"
          :loading="!isSearchForm && loadingSub"
        >
          <template #icon> <i class="iconfont icon-chaxun"></i> </template
          >{{ $t(`common.button.${getProps.submitButtonText}`) }}</n-button
        >
        <n-button v-if="getProps.showResetButton" v-bind="(getResetBtnOptions as any)" @click="resetFields">
          <template #icon> <i class="iconfont icon-zhongzhi"></i> </template
          >{{ $t(`common.button.${getProps.resetButtonText}`) }}</n-button
        >
        <n-button
          type="primary"
          text
          icon-placement="right"
          class="button-theme"
          v-if="isInline && showAdvancedButton"
          @click="unfoldToggle"
        >
          <template #icon>
            <n-icon size="14" class="unfold-icon" v-if="searchFormGridOpt.collapsed">
              <DownOutlined />
            </n-icon>
            <n-icon size="14" class="unfold-icon" v-else>
              <UpOutlined />
            </n-icon>
          </template>
          {{ $t(`common.form.${searchFormGridOpt.collapsed ? 'expand' : 'stow'}`) }}
        </n-button>
      </n-space>
    </div>
  </n-form>
</template>

<script lang="ts">
  import { defineComponent, reactive, ref, computed, unref, onMounted, watch } from 'vue';

  import { useFormEvents } from './hooks/useFormEvents';
  import { useFormValues } from './hooks/useFormValues';
  import DFFormItem from './Item.vue';

  import { basicProps } from './props';
  import { DownOutlined, UpOutlined, QuestionCircleOutlined } from '@vicons/antd';

  import type { Ref } from 'vue';
  import type { GridProps } from 'naive-ui/lib/grid';
  import type { FormSchema, FormProps, FormActionType } from './types/form';

  import { isArray } from '@/utils/is/index';
  import { deepMerge } from '@/utils';

  import { useMixins } from '@/hooks/useMixins';

  // 表单元素自适应宽度区间
  const responseWidthArr = [10, 768, 1100];

  export default defineComponent({
    name: 'BasicForm',
    components: { DownOutlined, UpOutlined, QuestionCircleOutlined, DFFormItem },
    props: {
      ...basicProps,
    },
    emits: ['reset', 'submit', 'register', 'change'],
    setup(props, { emit, attrs }) {
      const defaultFormModel = ref<any>({});
      const formModel = reactive<any>({});
      const propsRef = ref<Partial<FormProps>>({});
      const schemaRef = ref(null);
      const formElRef = ref(null);
      const gridCollapsed = ref(true);
      const loadingSub = ref(false);
      const isUpdateDefaultRef = ref(false);

      // 查询表单展开/搜索按钮自适应显示
      const showAdvancedButton = ref(false);
      const getResponseCols = () => {
        let cols = `${responseWidthArr.length}`;
        responseWidthArr.forEach((item, index) => {
          cols += ` ${item}:${index + 1}`;
        });
        return cols;
      };
      const searchFormGridOpt: any = reactive({
        cols: getResponseCols(),
        responsive: 'self',
        collapsed: gridCollapsed,
        xGap: 12,
      });

      const getSubmitBtnOptions = computed(() => {
        return Object.assign(
          {
            size: props.size,
            type: 'primary',
          },
          props.submitButtonOptions
        );
      });

      const getResetBtnOptions = computed(() => {
        return Object.assign(
          {
            size: props.size,
            type: 'default',
          },
          props.resetButtonOptions
        );
      });

      const { getTableLabelFn, getFormLabelFn } = useMixins();

      const getProps = computed((): FormProps => {
        const formProps = { ...props, ...unref(propsRef) } as FormProps;
        const rulesObj: any = {
          rules: {},
        };
        const schemas: any = formProps.schemas || [];
        schemas.forEach((item) => {
          if (item.rules && isArray(item.rules)) {
            rulesObj.rules[item.field] = item.rules;
          }
        });
        return { ...formProps, ...unref(rulesObj) };
      });

      const isInline = computed(() => {
        const { layout } = unref(getProps);
        return layout === 'inline';
      });

      const getGrid = computed((): GridProps => {
        const { gridProps } = unref(getProps);
        return {
          ...gridProps,
          collapsed: isInline.value ? gridCollapsed.value : false,
          responsive: 'screen',
          xGap: 20,
        };
      });

      const getBindValue = computed(
        () => ({ ...attrs, ...props, ...unref(getProps) } as any)
      );

      const getSchema = computed((): FormSchema[] => {
        const schemas: FormSchema[] = unref(schemaRef) || (unref(getProps).schemas as any);
        for (const schema of schemas) {
          const { defaultValue, component } = schema;
          // handle date type
          // dateItemType.includes(component as string)
          if (component === 'NDatePicker' || component === 'NTreeSelect') {
            schema.defaultValue = defaultValue;
          } else {
            if (defaultValue) {
              schema.defaultValue = defaultValue;
            }
          }
        }
        return schemas as FormSchema[];
      });

      const { handleFormValues, initDefault } = useFormValues({
        defaultFormModel,
        getSchema,
        formModel,
      });

      const { handleSubmit, validate, resetFields, getFieldsValue, clearValidate, setFieldsValue } =
        useFormEvents({
          emit,
          getProps,
          formModel,
          getSchema,
          formElRef: formElRef as Ref<FormActionType>,
          defaultFormModel,
          loadingSub,
          handleFormValues,
        });

      function unfoldToggle() {
        gridCollapsed.value = !gridCollapsed.value;
      }

      async function setProps(formProps: Partial<FormProps>): Promise<void> {
        propsRef.value = deepMerge(unref(propsRef) || {}, formProps);
      }

      const formActionType: Partial<FormActionType> = {
        getFieldsValue,
        setFieldsValue,
        resetFields,
        validate,
        clearValidate,
        setProps,
        submit: handleSubmit,
      };

      watch(
        () => getSchema.value,
        (schema) => {
          if (unref(isUpdateDefaultRef)) {
            return;
          }
          if (schema?.length) {
            initDefault();
            isUpdateDefaultRef.value = true;
          }
        }
      );
      watch(
        formModel,
        (data) => {
          emit('change', data);
        },
        { immediate: false }
      );

      // search-form resize 处理
      const handleShowAdvancedButton = (): void => {
        const searchFormDom: HTMLElement = document.querySelector('.search-form') as HTMLElement;
        if (searchFormDom) {
          const ow: number = searchFormDom.offsetWidth;
          const schemaLength: number = getSchema.value.length;
          showAdvancedButton.value = ow < responseWidthArr[schemaLength - 1] || schemaLength > 3;
          if (!showAdvancedButton.value && !searchFormGridOpt.collapsed) {
            searchFormGridOpt.collapsed = true;
          }
        }
      };

      onMounted(() => {
        initDefault();
        emit('register', formActionType);
        handleShowAdvancedButton();
        window.addEventListener('resize', handleShowAdvancedButton);
      });

      onUnmounted(() => {
        window.removeEventListener('resize', handleShowAdvancedButton);
      });

      return {
        formElRef,
        formModel,
        getGrid,
        getProps,
        searchFormGridOpt,
        getBindValue,
        getSchema,
        getSubmitBtnOptions,
        getResetBtnOptions,
        handleSubmit,
        resetFields,
        loadingSub,
        isInline,
        showAdvancedButton,
        unfoldToggle,
        setFieldsValue,
        setFieldsValueFn: setFieldsValue,
        getTableLabelFn,
        getFormLabelFn,
        validate,
        getFieldsValueFn: getFieldsValue,
      };
    },
  });
</script>

<style lang="less" scoped>
  .form-layout {
    display: flex;
    justify-content: space-between;
    &-wrap {
      width: 100%;
      &.search-form {
        width: calc(100% - 320px);
      }
    }
    &-btn {
      width: 300px;
      margin-top: 4px;
    }
  }
  :deep(.isFull) {
    width: 100%;
    justify-content: flex-start;
  }

  .unfold-icon {
    display: flex;
    align-items: center;
    height: 100%;
    margin-left: -3px;
  }

  .hidden {
    display: none;
  }
</style>

<style lang="less">
  .n-input__textarea-el {
    padding-right: 25px !important;
  }
  .fold-form.form-layout {
    // justify-content: flex-start;
    // .form-layout-wrap {
    //   width: 100%;
    //   &.search-form {
    //     width: auto;
    //   }
    // }
    .form-layout-btn {
      margin-top: 4px;
    }
    .n-form-item {
      padding-right: 8px;
      .n-form-item-label {
        width: auto !important;
      }
    }
  }

  .button-theme {
    color: @menuActiveColor;
    &:hover {
      color: @menuActiveColor !important;
      opacity: 0.9;
    }
    &:active {
      color: @menuActiveColor !important;
    }
    &:focus {
      color: @menuActiveColor !important;
    }
  }
</style>
